use chrono::Local;
use log::{debug, warn};
use opencv::imgcodecs;
use opencv::{
  core::{Mat, MatTraitConst, Rect},
  imgproc::{threshold, THRESH_BINARY},
};
use std::cmp;
use std::{
  error::Error,
  sync::{mpsc::Sender, Arc, Mutex},
};

use crate::{
  cv::Cv,
  window::{UIState, Window},
};

use super::options::{self, Food, Options, ShoeGlove};

pub mod destroyer;
pub mod walk;
pub mod wheel_barrow;
pub struct BaseFitneser {
  mutex: Arc<Mutex<bool>>,
  opts: Options,
  last_repair_shoe: i64,
  last_repair_glove: i64,
  temp_eat1: Mat,
  temp_eat2: Mat,
  temp_lost: Mat,
  temp_repair: Mat,
}

impl BaseFitneser {
  pub fn new(mutex: Arc<Mutex<bool>>) -> Self {
    BaseFitneser {
      mutex,
      opts: Options::default(),
      last_repair_shoe: 0,
      last_repair_glove: 0,
      temp_eat1: Mat::default(),
      temp_eat2: Mat::default(),
      temp_lost: Mat::default(),
      temp_repair: Mat::default(),
    }
  }

  pub fn setup(&mut self, opts: &Options, assets_path: String) {
    println!("{}", self.timestamp());
    // self.last_repair_shoe = self.timestamp();
    // self.last_repair_glove = self.timestamp();
    self.opts = opts.clone();
    let mut tmp_str = String::from(assets_path.clone());
    tmp_str += "/temp_eat1.jpg";
    if let Ok(img) = imgcodecs::imread(&tmp_str, imgcodecs::IMREAD_GRAYSCALE) {
      self.temp_eat1 = img;
    }
    tmp_str = String::from(assets_path.clone());
    tmp_str += "/temp_eat2.jpg";
    if let Ok(img) = imgcodecs::imread(&tmp_str, imgcodecs::IMREAD_GRAYSCALE) {
      self.temp_eat2 = img;
    }
    tmp_str = String::from(assets_path.clone());
    tmp_str += "/temp_lost.jpg";
    if let Ok(img) = imgcodecs::imread(&tmp_str, imgcodecs::IMREAD_GRAYSCALE) {
      self.temp_lost = img;
    }
    tmp_str = String::from(assets_path.clone());
    tmp_str += "/temp_repair.jpg";
    if let Ok(img) = imgcodecs::imread(&tmp_str, imgcodecs::IMREAD_GRAYSCALE) {
      self.temp_repair = img;
    }
  }

  /// 是否在运行
  pub fn is_running(&self) -> bool {
    let _t_mutex = Arc::clone(&self.mutex);
    let _running = *_t_mutex.lock().unwrap();
    _running
  }

  /// 确保不要死掉
  pub fn exit_no_die(&self, win: &Window) -> Result<bool, Box<dyn Error>> {
    win.ensure_ui(UIState::Tab, 3)?;
    let img = win.capture_rgb()?;
    let roi = img.roi(Rect::new(1210, 120, 35, 20))?;
    if !Cv::is_warn(&roi)? {
      return Ok(false);
    }
    warn!("生命值偏低，暂时退出");
    // 停止挂机
    self.stop_running();
    // 退出到主菜单
    win.ensure_ui(UIState::Esc, 0)?;
    win.sleep(250);
    win.mouse_move_to(570, 434)?;
    win.mouse_click_l()?;
    win.sleep(500);
    win.mouse_move_to(590, 378)?;
    win.mouse_click_l()?;
    Ok(true)
  }

  pub fn exit_to_lobby(&self, win: &Window) -> Result<(), Box<dyn Error>> {
    // 停止挂机
    self.stop_running();
    // 退出到主菜单
    win.ensure_ui(UIState::Esc, 0)?;
    win.sleep(250);
    win.mouse_move_to(570, 434)?;
    win.mouse_click_l()?;
    win.sleep(500);
    win.mouse_move_to(590, 378)?;
    win.mouse_click_l()?;
    Ok(())
  }

  /// 返回当前状态
  /// ### 返回值 位掩码
  /// * 1 << 0 = 1 卡路里
  /// * 1 << 1 = 2 水
  /// * 1 << 2 = 4 蛋白质
  /// * 1 << 3 = 8 结肠
  /// * 1 << 4 = 16 温度
  /// * 1 << 8 = 256 生命
  pub fn fetch_state(&self, win: &Window) -> Result<i32, Box<dyn Error>> {
    win.ensure_ui(UIState::Tab, 4)?;
    let mut state = 0;
    let img = win.capture_rgb()?;
    // 卡路里
    let mut roi = img.roi(Rect::new(1015, 40, 45, 30))?;
    if Cv::is_warn(&roi)? {
      state |= 1 << 0;
    }
    // 水
    roi = img.roi(Rect::new(1170, 40, 45, 30))?;
    if Cv::is_warn(&roi)? {
      state |= 1 << 1;
    }
    // 蛋白质
    roi = img.roi(Rect::new(965, 165, 40, 5))?;
    if Cv::is_warn(&roi)? {
      state |= 1 << 2;
    }
    // 结肠
    roi = img.roi(Rect::new(1135, 470, 35, 25))?;
    if Cv::is_warn(&roi)? {
      state |= 1 << 3;
    }
    // 温度
    roi = img.roi(Rect::new(250, 240, 10, 10))?;
    if Cv::is_warn(&roi)? {
      state |= 1 << 4;
    }
    // 生命值
    win.ensure_ui(UIState::Tab, 3)?;
    let img = win.capture_rgb()?;
    roi = img.roi(Rect::new(1210, 120, 35, 20))?;
    if Cv::is_warn(&roi)? {
      state |= 1 << 8;
    }
    Ok(state)
  }

  /// 确保 吃 喝 拉 血
  /// ### 返回值
  /// `true` 正常吃完
  /// `false` 退出
  pub fn ensure_base(&self, win: &Window, _state: i32) -> Result<bool, Box<dyn Error>> {
    // 拉撒
    if _state & 8 == 8 {
      debug!("排便");
      self.poo(win)?;
    }
    // 吃喝
    if !self.is_full(win)? {
      win.tap_key("tab")?;
      if _state & 4 == 4 {
        let foods = self.opts.foods.to_owned();
        debug!("补充: 蛋白质");
        if !self.eat(foods.into_iter().filter(|v| v.category == 2).collect(), win)? {
          return Ok(false);
        }
      }
      if _state & 1 == 1 {
        let foods = self.opts.foods.to_owned();
        debug!("补充: 卡路里");
        if !self.eat(foods.into_iter().filter(|v| v.category == 0).collect(), win)? {
          return Ok(false);
        }
      }
      if _state & 2 == 2 {
        let foods = self.opts.foods.to_owned();
        debug!("补充: 水分");
        if !self.eat(foods.into_iter().filter(|v| v.category == 1).collect(), win)? {
          return Ok(false);
        }
      }
    }

    Ok(true)
  }

  /// 判断是否需要维修
  pub fn is_needs_repair(&mut self) -> bool {
    let now = self.timestamp();
    let mut needed = false;
    if self.opts.shoe.enable {
      if now - self.last_repair_shoe > self.opts.shoe.interval * 60 * 1000 {
        needed = true;
      }
    }
    if self.opts.glove.enable {
      if now - self.last_repair_glove > self.opts.glove.interval * 60 * 1000 {
        needed = true;
      }
    }
    needed
  }

  /// 尝试维修
  pub fn try_repair(&mut self, win: &Window) -> Result<bool, Box<dyn Error>> {
    let now = self.timestamp();
    let mut applied = false;
    if self.opts.shoe.enable {
      if now - self.last_repair_shoe > self.opts.shoe.interval * 60 * 1000 {
        self.repair(&self.opts.shoe, win)?;
        self.last_repair_shoe = now;
        applied = true;
      }
    }
    if self.opts.glove.enable {
      if now - self.last_repair_glove > self.opts.glove.interval * 60 * 1000 {
        self.repair(&self.opts.glove, win)?;
        self.last_repair_glove = now;
        applied = true;
      }
    }
    Ok(applied)
  }

  /// 检查掉线
  pub fn check_lost(&self, win: &Window) -> Result<bool, Box<dyn Error>> {
    win.reset()?;
    win.sleep(500);
    let gray = win.capture()?;
    let roi = gray.roi(Rect::new(65, 435, 90, 30))?;
    let mut thres = Mat::default();
    threshold(&roi, &mut thres, 192.0, 255.0, THRESH_BINARY)?;
    let box_ref = thres.roi(Rect::new(0, 0, 90, 30))?;
    let (is_match, _) = Cv::is_match(&box_ref, &self.temp_lost)?;
    if is_match {
      debug!("连接丢失!");
    }
    Ok(is_match)
  }

  /// 检查并处理掉线
  pub fn handle_lost(&self, win: &Window) -> Result<bool, Box<dyn Error>> {
    // 等待并重连
    win.sleep(1000 * 60 * 1);
    for _ in 0..5 {
      // 关掉中间弹窗
      win.mouse_move_to(640, 375)?;
      win.sleep(350);
      win.mouse_click_l()?;
      // 等待
      win.sleep(1000 * 30);
      debug!("尝试重连");
      win.mouse_move_to(110, 450)?;
      win.sleep(350);
      win.mouse_click_l()?;
      // 等待
      for _ in 0..5 {
        win.sleep(1000 * 30);
        if self.check_lost(win)? {
          break;
        }
        win.ensure_ui(UIState::Esc, 0)?;
        if win.ui_state()? == UIState::Esc {
          win.sleep(500);
          return Ok(true);
        }
      }
    }
    Ok(false)
  }

  /// 停止挂机
  pub fn stop_running(&self) {
    let _mutex = Arc::clone(&self.mutex);
    let mut _running = _mutex.lock().unwrap();
    *_running = false;
  }

  /// 修东西
  /// ### 返回值
  /// `true` 正常
  /// `false` 退出
  fn repair(&self, item: &ShoeGlove, win: &Window) -> Result<bool, Box<dyn Error>> {
    if item.key != 5 && item.key != 6 {
      return Ok(true);
    }
    if !self.is_running() {
      return Ok(false);
    }
    // 拿工具
    let mut _drop_x = 0;
    let mut _drop_y = 0;
    if self.opts.toolkits.len() > 0 {
      // 打开箱子
      self.open_chest(win)?;
      let mut res = None;
      for col in self.opts.toolkits.iter() {
        res = self.pick_from_chest(*col, win)?;
        if res.is_none() {
          continue;
        }
        break;
      }
      if res.is_none() {
        debug!("没有拿到修理工具 {}", item.key);
        return Ok(false);
      }
      let (x, y) = res.unwrap();
      _drop_x = x;
      _drop_y = y;
    }
    // 修理
    let index = [0, 0, 0, 3, 2, 1, 0][item.key as usize];
    win.ensure_ui(UIState::Tab, 1)?;
    let _x = 1210 - index * 80;
    let mut _y = 410;
    win.mouse_move_to(_x, _y)?;
    win.mouse_click_r()?;
    win.sleep(250);
    // _y += item.y_offset;
    // win.mouse_move_to(_x, _y)?;
    // win.sleep(125);
    let img = win.capture()?;
    let roi_x = cmp::min(_x - 15, 1280 - 80);
    let roi = img.roi(Rect::new(roi_x, _y, cmp::min(1280 - roi_x, 80), 100))?;
    let (is_match, loc) = Cv::is_match(&roi, &self.temp_repair)?;
    if is_match {
      win.mouse_move_to(roi_x + loc.x, _y + loc.y + 8)?;
      win.sleep(45);
      win.mouse_click_l()?;
      debug!("修理: {}", ["鞋子", "手套"][index as usize]);
      win.sleep(4500);
    } else {
      win.mouse_move_to(640, 360)?;
      win.mouse_click_l()?;
      debug!("{}: 无需修理或无法修理", ["鞋子", "手套"][index as usize]);
    }
    // 修理工具放回去
    if self.opts.toolkits.len() > 0 {
      self.hand_to_chest(_drop_x, _drop_y, win)?;
      // 从 435 90 位置
      for _ in 0..5 {
        self.drop_to_chest((435, 90), (_drop_x, _drop_y), win)?
      }
    }
    Ok(true)
  }

  /// 吃东西
  /// ### 返回值
  /// `true` 正常吃完
  /// `false` 退出
  fn eat(&self, foods: Vec<Food>, win: &Window) -> Result<bool, Box<dyn Error>> {
    if foods.len() < 1 {
      return Ok(true);
    }
    if self.opts.eat_type == 0 {
      for _ in 0..foods[0].repeat {
        if !self.is_running() {
          return Ok(false);
        }
        win.ensure_ui(UIState::Default, 0)?;
        win.tap_key(&foods[0].key.to_string())?;
        win.sleep(foods[0].time_cast as u64);
      }
    } else {
      debug!("计划吃 {} {}", foods[0].category, foods[0].repeat);
      // 打开箱子
      self.open_chest(win)?;
      // 找到食物 318 - 757  238 - 699
      // 440 / 20 = 22
      // 21 * 22 = 462  461
      let mut _repeat = foods[0].repeat;
      for food in foods.iter() {
        let res = self.pick_from_chest(food.key as i32, win)?;
        if res.is_none() {
          continue;
        }
        let (_x, _y) = res.unwrap();
        let mut _turn_back = false;
        // 吃
        loop {
          if !self.is_running() {
            break;
          }
          // 检查手里有没有
          let img = win.capture()?;
          if !Cv::has_non_zero(&img, 990, 580, 16, 16, 192.0)? {
            // println!("空手");
            _turn_back = true;
            break;
          }
          // 右键菜单
          win.mouse_move_to(975, 605)?;
          win.sleep(50);
          win.mouse_click_r()?;
          win.sleep(250);
          let img = win.capture()?;
          // 找 吃/喝 一口
          let roi = img.roi(Rect::new(970, 605, 50, 100))?;
          let mut res = Cv::is_match(&roi, &self.temp_eat1)?;
          if !res.0 {
            res = Cv::is_match(&roi, &self.temp_eat2)?;
          }
          // 不能吃的东西？拿错了 忽略
          if !res.0 {
            // println!("没找到吃喝一口");
            break;
          }
          win.mouse_move_to(980, 605 + res.1.y)?;
          win.sleep(125);
          win.mouse_click_l()?;
          win.sleep(food.time_cast as u64);
          debug!("已经吃 {} {}", food.category, _repeat);
          _repeat -= 1;
          if _repeat < 1 {
            break;
          }
        }
        // 放回去
        if !_turn_back {
          _turn_back = self.hand_to_chest(_x, _y, win)?;
        }
        if !self.is_running() {
          return Ok(false);
        }
        if _repeat < 1 {
          break;
        }
      }
    }
    Ok(true)
  }

  /// 拉屎
  fn poo(&self, win: &Window) -> Result<(), Box<dyn Error>> {
    win.ensure_ui(UIState::Default, 0)?;
    win.mouse_click_r()?;
    win.release_key("tab")?;
    win.press_key("tab")?;
    win.sleep(1000);
    win.mouse_move_to(715, 355)?;
    win.sleep(50);
    win.mouse_move(5, 0)?;
    win.mouse_click_l()?;
    win.sleep(125);
    win.mouse_move_to(760, 345)?;
    win.sleep(50);
    win.mouse_move(5, 0)?;
    win.sleep(100);
    let (x, y) = win.mouse_position()?;
    if win.is_active(x, y) {
      win.mouse_click_l()?;
      win.sleep(35000);
    }
    Ok(())
  }

  /// 是否饱了
  fn is_full(&self, win: &Window) -> Result<bool, Box<dyn Error>> {
    win.ensure_ui(UIState::Tab, 4)?;
    let img = win.capture_rgb()?;
    let roi = img.roi(Rect::new(980, 470, 30, 25))?;
    if Cv::is_warn(&roi)? {
      return Ok(true);
    }
    Ok(false)
  }

  /// 切换状态页面
  fn look_status(&self, win: &Window) -> Result<(), Box<dyn Error>> {
    win.ensure_ui(UIState::Tab, 4)?;
    win.mouse_move_to(45, 60)?;
    win.sleep(200);
    win.mouse_click_r()?;
    win.mouse_move(5, 5)?;
    Ok(())
  }

  /// 从箱子拿到手里
  fn pick_from_chest(&self, col: i32, win: &Window) -> Result<Option<(i32, i32)>, Box<dyn Error>> {
    // println!("pickup from {}", col);
    // 寻找
    let _x = (col - 1) * 22 + 318 + 5;
    let mut _y = 238 + 11;
    for _ in 0..9 {
      win.mouse_move_to(_x, _y)?;
      win.sleep(50);
      win.mouse_click_r()?;
      win.sleep(250);
      // 如果拿起物品可用
      if win.is_active(_x, _y) {
        // 拿上
        win.mouse_click_l()?;
        win.sleep(750);
        return Ok(Some((_x, _y)));
      } else {
        win.mouse_move_to(640, 190)?;
        win.sleep(25);
        win.mouse_click_l()?;
      }
      _y += 22 * 2;
    }
    Ok(None)
  }

  /// 从手里放到箱子里
  fn hand_to_chest(&self, x: i32, y: i32, win: &Window) -> Result<bool, Box<dyn Error>> {
    for _ in 0..10 {
      self.drop_to_chest((975, 605), (x, y), win)?;
      win.sleep(125);
      // 检查手里有没有东西
      let img = win.capture()?;
      if !Cv::has_non_zero(&img, 990, 580, 16, 16, 192.0)? {
        return Ok(true);
      }
      win.sleep(125);
    }
    Ok(false)
  }

  /// 拖到箱子里
  fn drop_to_chest(
    &self,
    from: (i32, i32),
    to: (i32, i32),
    win: &Window,
  ) -> Result<(), Box<dyn Error>> {
    win.mouse_move_to(from.0, from.1)?;
    win.sleep(125);
    win.mouse_press_l()?;
    win.sleep(125);
    win.mouse_move_to(to.0, to.1)?;
    win.sleep(125);
    win.mouse_release_l()?;
    win.sleep(125);
    Ok(())
  }

  /// 打开箱子 箱子必须位于第一的位置
  fn open_chest(&self, win: &Window) -> Result<bool, Box<dyn Error>> {
    win.ensure_ui(UIState::Tab, 1)?;
    win.mouse_move_to(360, 190)?;
    win.mouse_click_r()?;
    win.sleep(250);
    win.mouse_move_to(360, 190 + 18)?;
    win.sleep(50);
    win.mouse_click_l()?;
    win.sleep(250);
    Ok(true)
  }

  fn timestamp(&self) -> i64 {
    let dt = Local::now();
    dt.timestamp_millis()
  }
}

pub trait Fitneser {
  fn init(&mut self, opts: &options::Options, assets_path: String) -> Result<bool, Box<dyn Error>>;
  fn run(&mut self, win: &Window, msg_tx: &Sender<String>) -> Result<bool, Box<dyn Error>>;
}
